#include "QRenderProgram.h"

QRenderAttribute::QRenderAttribute()
{
    this->id = (unsigned int)0;
    this->number = (unsigned int)0;
    this->target = (unsigned int)0;
    this->size = (unsigned int)0;
    this->name.assign(std::string());
}

QRenderAttribute::QRenderAttribute(unsigned int id, unsigned int number, unsigned int target, unsigned int size, const std::string &name)
{
    this->id = id;
    this->number = number;
    this->target = target;
    this->size = size;
    this->name = name;
}

QRenderAttribute::~QRenderAttribute()
{

}

int QRenderAttribute::findAttribute(std::vector<QRenderAttribute> &attributes, const char *name, int start)
{
    for (int i = start; i < attributes.size(); i++)
    {
        if (attributes.at(i).name.find(name) != std::string::npos) return i;
    }
    return -1;
}

QRenderShader::QRenderShader()
{
    this->id = (unsigned int)0;
    this->target = (unsigned int)0;
    this->name.assign(std::string());
}

QRenderShader::QRenderShader(unsigned int target, const std::string &name)
{
    this->id = (unsigned int)0;
    this->target = target;
    this->name = name;
}

QRenderShader::~QRenderShader()
{

}

unsigned char QRenderShader::compile()
{
    this->id = glCreateShader(this->target);
    
    std::string content;
    if (!QRenderIO::getFileContent(this->name, content)) return GL_FALSE;
    
    const char *pointer = content.c_str();
    glShaderSource(this->id, 1, &pointer, NULL);
    glCompileShader(this->id);
    
    QRenderUtility::printShaderInfoLog(this->id);
    if (!QRenderUtility::checkShaderStatus(this->id, GL_COMPILE_STATUS)) return GL_FALSE;

    return GL_TRUE;
}

int QRenderShader::findShader(std::vector<QRenderShader> &shaders, const char *name, int start)
{
    for (int i = start; i < shaders.size(); i++)
    {
        if (shaders.at(i).name.find(name) != std::string::npos) return i;
    }
    return -1;
}

QRenderProgram::QRenderProgram()
{
    this->id = (unsigned int)0;
    this->path.assign(std::string());
    this->currentShaders.assign(0, (int)0);
    this->index.assign(0, (unsigned char)0);
    this->vertex.assign(0, QRenderVertex());
    this->shaders.assign(0, QRenderShader());
    this->textures.assign(0, QRenderTexture());
}

QRenderProgram::QRenderProgram(unsigned int id, const std::string &path)
{
    this->id = id;
    this->path = path;
    this->currentShaders.assign(0, (int)0);
    this->index.assign(0, (unsigned char)0);
    this->vertex.assign(0, QRenderVertex());
    this->shaders.assign(0, QRenderShader());
    this->textures.assign(0, QRenderTexture());
}

QRenderProgram::~QRenderProgram()
{

}

void QRenderProgram::addProgram(std::vector<QRenderProgram> &programs, const std::string &path)
{
    unsigned int id = glCreateProgram();
    QRenderProgram program = QRenderProgram(id, path);
    programs.push_back(program);
}


void QRenderProgram::addIndex(unsigned char x, unsigned char y, unsigned char z, unsigned char w)
{
    this->index.push_back(x);
    this->index.push_back(y);
    this->index.push_back(z);
    this->index.push_back(w);
}

void QRenderProgram::addVertex(const QRenderVertex &vertex)
{
    this->vertex.push_back(vertex);
}

void QRenderProgram::addAttribute(unsigned int id, unsigned int number, unsigned int target, unsigned int size, const std::string &name)
{
    int index = this->offsets.size() - 1;
    int offset = index == -1 ? 0 : this->offsets.at(index) + this->attributes.at(index).size;
    this->offsets.push_back(offset);
    
    QRenderAttribute attribute = QRenderAttribute(id, number, target, size, name);
    this->attributes.push_back(attribute);
}

void QRenderProgram::addShader(unsigned int target, const std::string &name)
{
    std::string file = name;
    switch (target)
    {
    case GL_VERTEX_SHADER:
        file += ".vsh";
        break;
    case GL_GEOMETRY_SHADER:
        file += ".gsh";
        break;
    case GL_FRAGMENT_SHADER:
        file += ".psh";
        break;
    }
    QRenderShader shader = QRenderShader(target, file);
    this->shaders.push_back(shader);
}

void QRenderProgram::enableTextures()
{
    int size = this->textures.size();
    for (int i = 0; i < size; i++)
    {
        const QRenderTexture *texture = &textures.at(i);
        glActiveTexture(GL_TEXTURE0 + texture->unit);
        glBindTexture(texture->target, texture->id);
    }
}

void QRenderProgram::disableTextures()
{
    int size = this->textures.size();
    for (int i = 0; i < size; i++)
    {
        const QRenderTexture *texture = &textures.at(i);
        glActiveTexture(GL_TEXTURE0 + texture->unit);
        glDisable(texture->target);
    }
}

unsigned char QRenderProgram::assign(const std::vector<int> &shaderIndices, const std::vector<int> &attributeIndices)
{
    if (!this->attachShaders(shaderIndices)) return GL_FALSE;
    if (!this->attachAttributes(attributeIndices)) return GL_FALSE;
    if (!this->link()) return GL_FALSE;

    return GL_TRUE;
}

void QRenderProgram::clear()
{
    this->detachShaders();
    this->detachAttributes();
}

void QRenderProgram::destroy()
{
    int textureSize = textures.size();
    for (int i = 0; i < textureSize; i++)
    {
        QRenderTexture *texture = &textures.at(i);
        glDeleteTextures(1, &texture->id);
    }
	textures.clear();
    
    int shaderSize = shaders.size();
    for (int i = 0; i < shaderSize; i++)
    {
        QRenderShader *shader = &shaders.at(i);
        glDeleteShader(shader->id);
    }
	shaders.clear();

    glDeleteProgram(this->id);
}

void QRenderProgram::activate()
{
    this->enableTextures();
    this->enableAttributes();
    glUseProgram(this->id);
}

void QRenderProgram::deactivate()
{
    this->disableTextures();
    this->disableAttributes();
    glUseProgram(0);
}

unsigned char QRenderProgram::compileShaders()
{
    int size = this->currentShaders.size();
    for (int i = 0; i < size; i++)
    {
        QRenderShader *pointer = &this->shaders.at(this->currentShaders.at(i));
        if (!pointer->compile()) return GL_FALSE;
    }
    
    return GL_TRUE;
}

unsigned char QRenderProgram::link()
{
    glLinkProgram(this->id);
    if (!QRenderUtility::checkError(__FILE__, __LINE__)) return GL_FALSE;
    
    QRenderUtility::printProgramInfoLog(this->id);
    if (!QRenderUtility::checkShaderStatus(this->id, GL_LINK_STATUS)) return GL_FALSE;
    
    return GL_TRUE;
}

unsigned char QRenderProgram::attachShaders(const std::vector<int> &indices)
{
    this->currentShaders = indices;
    if (!this->compileShaders()) return GL_FALSE;
    
    int size = this->currentShaders.size();
    for (int i = 0; i < size; i++)
    {
        QRenderShader *pointer = &this->shaders.at(this->currentShaders.at(i));
        glAttachShader(this->id, pointer->id);
    }
    
    return GL_TRUE;
}

void QRenderProgram::detachShaders()
{
    int size = this->currentShaders.size();
    for (int i = 0; i < size; i++)
    {
        QRenderShader *pointer = &this->shaders.at(this->currentShaders.at(i));
        glDetachShader(this->id, pointer->id);
    }
    this->currentShaders.clear();
}

unsigned char QRenderProgram::attachAttributes(const std::vector<int> &indices)
{
    this->currentAttributes = indices;
    
    int size = this->currentAttributes.size();
    for (int i = 0; i < size; i++)
    {
        QRenderAttribute *attribute = &this->attributes.at(this->currentAttributes.at(i));
        glBindAttribLocation(this->id, attribute->id, attribute->name.c_str());
    }
    
    return GL_TRUE;
}

void QRenderProgram::detachAttributes()
{
    this->currentAttributes.clear();
}

void QRenderProgram::enableAttributes()
{
    int currentNumber = this->currentAttributes.size();
    for (int i = 0; i < currentNumber; i++)
    {
        int index = this->currentAttributes.at(i);
        QRenderAttribute *attribute = &this->attributes.at(index);
        glVertexAttribPointer(attribute->id, attribute->number, attribute->target, 0, sizeof(QRenderVertex), (char *)NULL + this->offsets.at(index));
        glEnableVertexAttribArray(attribute->id);
    }
}

void QRenderProgram::disableAttributes()
{
    int currentNumber = this->currentAttributes.size();
    for (int i = 0; i < currentNumber; i++)
    {
        QRenderAttribute *attribute = &this->attributes.at(this->currentAttributes.at(i));
        glDisableVertexAttribArray(attribute->id);
    }
}

void QRenderProgram::clearShader()
{
    int size = this->currentShaders.size();
    for (int i = 0; i < size; i++)
    {
        QRenderShader *pointer = &this->shaders.at(this->currentShaders.at(i));
        glDetachShader(this->id, pointer->id);
    }
    this->currentShaders.clear();

    int shaderSize = shaders.size();
    for (int i = 0; i < shaderSize; i++)
    {
        QRenderShader *shader = &shaders.at(i);
        glDeleteShader(shader->id);
    }
    //shaders.clear();
}